using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace ThaiLanguageTools
{
    public class ChangeRule
    {
        const decimal DefaultProbability = 0.9m;
        public const char MaskChar = '#';


        #region Xml
        [XmlAttribute("src")]
        public String Src;
        [XmlAttribute("dest")]
        public String Dest;
        [XmlAttribute("prob")]
        public decimal Probability = DefaultProbability;
        [XmlIgnore]
        public bool ProbabilitySpecified
        {
            get { return Probability != DefaultProbability; }
            set { } // XML bug
        }
        [XmlAttribute("comment")]
        public String Comment;
        
        public ChangeRule() { } // XML
        #endregion

        string _destMask;
        [XmlIgnore]
        public string DestMask
        {
            get
            {
                if (_destMask == null) { _destMask = new String(MaskChar, Dest.Length); }
                return _destMask;
            }
        }

        public ChangeRule(String src, String dest)
        {
            Src = src;
            Dest = dest;
        }

        // Rule application
        //  * Apply replacement to each char (e.g. s->t, sasa -> {[t]asa,sa[t]a})
        //  * Never apply to already tokenized (e.g. s->t [s]asa -> [s]a[t]a, but NOT [[t]]asa)

        /// <summary>
        /// Apply the rule to the target, filling in the list of queries with results.
        /// </summary>
        /// <param name="target"></param>
        /// <param name="queries"></param>
        public void ApplyRule(QueryList queries)
        {
            // Keep applying the rule to all queries until it can no longer
            // be applied. 
            // TODO: consider having a probability threshold
            while (true)
            {
                List<QueryInfo> qlCopy = queries.GetQueryListCopy(false);

                // Terribly inefficient, schlemiel-the-painter algorithm.
                // We can skip the queries that we already processed, but order
                // in the list is not guaranteed, so hard to keep track
                foreach (QueryInfo query in qlCopy)
                {
                    int index = 0;
                    while (index >= 0)
                    {
                        QueryInfo newQuery = query.ReplaceOnce(ref index, this);
                        if (newQuery == null) { break; }
                        queries.TryAddQuery(newQuery); 
                    }
                }
                // If the list stopped changing, we're done
                if (queries.Count == qlCopy.Count) { break; }
            }
        }

        #region Parsing
        const String SComment = "//";
        const String SSep = "->";

        public static ChangeRule Parse(String line)
        {
            ChangeRule r = new ChangeRule();

            String content;
            Utils.SplitInTwo(line, SComment, out content, out r.Comment);
            r.Comment = r.Comment.Trim();
            content = content.Trim();
            if (content.Length == 0) { return null; } // comment or empty line

            Utils.SplitInTwo(content, SSep, out r.Src , out r.Dest);
            r.Src = r.Src.Trim().ToLower();
            r.Dest = r.Dest.Trim().ToLower();
            if (String.IsNullOrEmpty(r.Src) || String.IsNullOrEmpty(r.Dest))
            {
                throw new FormatException("Invalid rule: " + line);
            }

            return r;
        }

        public String ToStringRep()
        {
            return Src + "->" + Dest +
                (String.IsNullOrEmpty(Comment) ? String.Empty : " // " + Comment);
        }
        #endregion

        public override string ToString()
        {
            return ToStringRep();
        }

        public override bool Equals(object obj)
        {
            ChangeRule that = obj as ChangeRule;
            if (that == null) { return false; }
            // Intentionally ignoring probability / comment
            return this.Src == that.Src && this.Dest == that.Dest;
        }

    }
}
